﻿using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using Xant.Querier.Core;


namespace Xant.Querier.Utils
{
    public enum Format
    {
        Xml,
        Compact
    }

    public static class QuerySerializer
    {
        public static string Serialize(Query query, Format format)
        {
            if (format == Format.Xml)
            {
                return query.ToXml();
            }
            else
            {
                return query.ToJson();
            }
        }

        public static Query Deserialize(string queryStr)
        {
            if (queryStr == null)
            {
                throw new ArgumentNullException("queryStr");
            }
            if (queryStr.StartsWith("<") && queryStr.EndsWith(">"))
            {
                return FromXml(queryStr);
            }
            else if (queryStr.StartsWith("[") && queryStr.EndsWith("]"))
            {
                throw new NotImplementedException();
            }
            else
            {
                throw new Exception("未能识别的序列化格式。");
            }
        }

        #region 序列化为XML格式

        private static string ToXml(this Query query)
        {
            var doc = new XmlDocument();
            var queryElement = doc.CreateElement("Query");
            doc.AppendChild(queryElement);
            var attrSourceEntityType = doc.CreateAttribute("SourceEntityType");
            attrSourceEntityType.Value = query.SourceEntityType.FullName;
            queryElement.Attributes.Append(attrSourceEntityType);
            var attrAssembly = doc.CreateAttribute("ReferenceAssembly");
            attrAssembly.Value = query.SourceEntityType.Assembly.FullName;
            queryElement.Attributes.Append(attrAssembly);
            if (!string.IsNullOrEmpty(query.Name))
            {
                var attrName = doc.CreateAttribute("Name");
                attrName.Value = query.Name;
                queryElement.Attributes.Append(attrName);
            }
            if (!string.IsNullOrEmpty(query.Remark))
            {
                var attrRemark = doc.CreateAttribute("Remark");
                attrRemark.Value = query.Remark;
                queryElement.Attributes.Append(attrRemark);
            }
            if (query.RootExpression != null)
            {
                query.RootExpression.ToXml(doc.DocumentElement);
            }
            if (query.OrderClause != null)
            {
                query.OrderClause.ToXml(doc.DocumentElement);
            }
            if (query.Pagination != null)
            {
                query.Pagination.ToXml(doc.DocumentElement);
            }
            var sb = new StringBuilder();
            using (var sw = new StringWriter(sb))
            {
                var writer = new XmlTextWriter(sw) {Formatting = Formatting.Indented, Indentation = 4, IndentChar = ' '};
                doc.WriteContentTo(writer);
                writer.Close();
            }
            return sb.ToString();
        }

        private static void ToXml(this Xpression expression, XmlNode parentNode)
        {
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }
            var doc = parentNode.OwnerDocument;
            if (doc == null)
            {
                throw new Exception("parentNode.OwnerDocument is null");
            }
            var element = doc.CreateElement(expression.GetType().Name);
            parentNode.AppendChild(element);
            if (expression is Criteria)
            {
                (expression as Criteria).ToXml(element);
            }
            else if (expression is CriteriaNegation)
            {
                (expression as CriteriaNegation).ToXml(element);
            }
            else if (expression is CriteriaPair)
            {
                (expression as CriteriaPair).ToXml(element);
            }
        }

        private static void ToXml(this Criteria criteria, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrOperator = doc.CreateAttribute("Operator");
            attrOperator.Value = criteria.Operator.ToString();
            element.Attributes.Append(attrOperator);
            criteria.Factor.ToXml(element);
            criteria.Value.ToXml(element);
        }

        private static void ToXml(this CriteriaNegation negation, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrOperator = doc.CreateAttribute("Operator");
            attrOperator.Value = negation.Operator.ToString();
            element.Attributes.Append(attrOperator);
            negation.Body.ToXml(element);
        }

        private static void ToXml(this CriteriaPair pair, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrOperator = doc.CreateAttribute("Operator");
            attrOperator.Value = pair.Operator.ToString();
            element.Attributes.Append(attrOperator);
            pair.Left.ToXml(element);
            pair.Right.ToXml(element);
        }

        private static XmlElement ToXml(this Factor factor, XmlNode parentNode)
        {
            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }
            var doc = parentNode.OwnerDocument;
            if (doc == null)
            {
                throw new Exception("parentNode.OwnerDocument is null");
            }
            var element = doc.CreateElement(factor.GetType().Name);
            parentNode.AppendChild(element);
            if (factor is Constant) //常量
            {
                (factor as Constant).ToXml(element);
            }
            else if (factor is Macro) //宏
            {
                (factor as Macro).ToXml(element);
            }
            else if (factor is Field) //字段
            {
                (factor as Field).ToXml(element);
            }
            else if (factor is Formular) //算式
            {
                (factor as Formular).ToXml(element);
            }
            else
            {
                throw new Exception("不支持的因子类型：" + factor.GetType().FullName);
            }
            return element;
        }

        private static void ToXml(this Field field, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrPath = doc.CreateAttribute("Path");
            var attrType = doc.CreateAttribute("Type");
            attrPath.Value = field.Path;
            attrType.Value = field.Type.FullName;
            element.Attributes.Append(attrPath);
            element.Attributes.Append(attrType);
        }

        private static void ToXml(this Macro macro, XmlElement element)
        {

        }

        private static void ToXml(this Constant constant, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrValue = doc.CreateAttribute("Value");
            attrValue.Value = constant.ToString();
            element.Attributes.Append(attrValue);
            if (constant.Value != null)
            {
                var attrType = doc.CreateAttribute("Type");
                attrType.Value = constant.Value.GetType().FullName;
                element.Attributes.Append(attrType);
            }
        }

        private static void ToXml(this Formular formular, XmlElement element)
        {
            var doc = element.OwnerDocument;
            var attrOperator = doc.CreateAttribute("Operator");
            attrOperator.Value = formular.Operator.ToString();
            element.Attributes.Append(attrOperator);
            formular.Left.ToXml(element);
            if (formular.Right != null)
            {
                formular.Right.ToXml(element);
            }
        }

        private static void ToXml(this OrderClause orderClause, XmlNode parentNode)
        {
            if(orderClause==null || !orderClause.HasElements)
                return;
            var doc = parentNode.OwnerDocument;
            var elementOrder = parentNode.AppendChild(doc.CreateElement("OrderBy"));
            foreach (var orderElement in orderClause.Elements)
            {
                orderElement.ToXml(elementOrder);
            }
        }

        private static void ToXml(this OrderElement orderElement, XmlNode parentNode)
        {
            var xmlElement = orderElement.Factor.ToXml(parentNode);
            var attrRule = parentNode.OwnerDocument.CreateAttribute("Rule");
            attrRule.Value = orderElement.Rule.ToString();
            xmlElement.Attributes.Append(attrRule);
        }

        private static void ToXml(this Pagination pagination, XmlNode parentNode)
        {
            if(pagination==null)
                return;
            var doc = parentNode.OwnerDocument;
            var element = parentNode.AppendChild(doc.CreateElement("Pagination"));
            var attrPageSize = parentNode.OwnerDocument.CreateAttribute("PageSize");
            attrPageSize.Value = pagination.PageSize.ToString();
            var attrPageIndex = parentNode.OwnerDocument.CreateAttribute("PageIndex");
            attrPageIndex.Value = pagination.PageIndex.ToString();
            element.Attributes.Append(attrPageSize);
            element.Attributes.Append(attrPageIndex);
        }

        #endregion

        #region 序列化为Json格式

        private static string ToJson(this Query query)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region 反序列化(XML格式)

        /// <summary>
        /// 移动到下一个元素
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="abortWhenEndElement">读取到指定的元素终止节点时中止循环</param>
        /// <returns></returns>
        private static bool MoveToNextElement(this XmlReader reader, string abortWhenEndElement=null)
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    return true;
                }
                if (abortWhenEndElement != null && reader.NodeType == XmlNodeType.EndElement && reader.Name.Equals(abortWhenEndElement))
                {
                    break;
                }
            }
            return false;
        }

        private static Query FromXml(string xml)
        {
            Query query = null;
            using (var stringReader = new StringReader(xml))
            {
                var reader = new XmlTextReader(stringReader);
                while (reader.MoveToNextElement())
                {
                    if ("Query".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
                    {
                        query = FromXml(reader);
                    }
                }
                reader.Close();
            }
            return query;
        }

        private static Query FromXml(XmlReader reader)
        {
            var name = reader.Name;
            if (!"Query".Equals(name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Query节点");
            }
            if (!reader.MoveToAttribute("SourceEntityType"))
            {
                throw new Exception("节点中未找到SourceEntityType属性，为非法Query节点");
            }
            string sourceEntityType = reader.Value;
            if (!reader.MoveToAttribute("ReferenceAssembly"))
            {
                throw new Exception("节点中未找到ReferenceAssembly属性，为非法Query节点");
            }
            string assemblyReference = reader.Value;
            var assembly = Assembly.Load(assemblyReference);
            if (assembly == null)
            {
                throw new Exception("查询对象所参照的程序集加载失败：" + assemblyReference);
            }
            var type = assembly.GetType(sourceEntityType);
            var query = new Query(type);
            if (reader.MoveToAttribute("Name"))
            {
                query.Name = reader.Value;
            }
            if (reader.MoveToAttribute("Remark"))
            {
                query.Remark = reader.Value;
            }
            while (reader.MoveToNextElement())
            {
                if (reader.Name.StartsWith("Criteria", StringComparison.InvariantCultureIgnoreCase))
                {
                    Xpression expression = ReadExpression(reader);
                    query.RootExpression = expression;
                }
                else if (reader.Name.Equals("OrderBy", StringComparison.InvariantCultureIgnoreCase))
                {
                    OrderClause orderCluase = ReadOrderClause(reader);
                    query.OrderClause = orderCluase;
                }
                else if (reader.Name.Equals("Pagination", StringComparison.InvariantCultureIgnoreCase))
                {
                    Pagination pagination = ReadPagination(reader);
                    query.Pagination = pagination;
                }
            }
            return query;
        }

        private static T ParseOperator<T>(string sOperator) where T : struct
        {
            var type = typeof (T);
            if (!type.IsEnum)
            {
                throw new ArgumentException("T必须为枚举类型");
            }
            return (T)Enum.Parse(type, sOperator);
        }

        private static Xpression ReadExpression(XmlReader reader)
        {
            switch (reader.Name.ToLower())
            {
                case "criteria":
                    return ReadCriteria(reader);
                case "criterianegation":
                    return ReadCriteriaNegation(reader);
                case "criteriapair":
                    return ReadCriteriaPair(reader);
                default:
                    throw new FormatException("未识别的条件表达式类型：" + reader.Name);
            }
        }

        private static Criteria ReadCriteria(XmlReader reader)
        {
            if (!"Criteria".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Criteria节点");
            }
            if (!reader.MoveToAttribute("Operator"))
            {
                throw new FormatException("节点未包含Operator属性");
            }
            string sOperator = reader.Value;
            var operatr = ParseOperator<RelationalOperator>(sOperator);
            Factor factor = null, value=null;
            if (reader.MoveToNextElement())//移动到Factor
            {
                factor = ReadFactor(reader);
            }
            if (reader.MoveToNextElement())//移动到Value
            {
                value = ReadFactor(reader);
            }
            return new Criteria(factor, operatr, value);
        }

        private static CriteriaNegation ReadCriteriaNegation(XmlReader reader)
        {
            if (!"CriteriaNegation".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非CriteriaNegation节点");
            }
            /*//CriteriaNegation节点的操作符始终为UnaryLogicalOperator.Not，所以无需读取
            if (!reader.MoveToAttribute("operator"))
            {
                throw new FormatException("节点未包含Operator属性");
            }
            string sOperator = reader.Value;
            var operatr = ParseOperator<UnaryLogicalOperator>(sOperator);*/
            Xpression expression = null;
            if (reader.MoveToNextElement())//移动到Body
            {
                expression = ReadCriteria(reader);
            }
            return new CriteriaNegation(expression);
        }

        private static CriteriaPair ReadCriteriaPair(XmlReader reader)
        {
            if (!"CriteriaPair".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非CriteriaPair节点");
            }
            if (!reader.MoveToAttribute("Operator"))
            {
                throw new FormatException("节点未包含Operator属性");
            }
            string sOperator = reader.Value;
            var operatr = ParseOperator<LogicalOperator>(sOperator);

            Xpression left = null, right = null;
            if (reader.MoveToNextElement())//移动到Left
            {
                left = ReadExpression(reader);
            }
            if (reader.MoveToNextElement())//移动到Right
            {
                right = ReadExpression(reader);
            }
            return new CriteriaPair(left, operatr, right);
        }

        private static Factor ReadFactor(XmlReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }
            switch (reader.Name.ToLower())
            {
                case "constant"://常量
                    return ReadConstant(reader);
                case "macro"://宏
                    return ReadMacro(reader);
                case "field"://字段
                    return ReadField(reader);
                case "formular"://算式
                    return ReadFormular(reader);
                default:
                    throw new FormatException("未能识别的因子类型："+reader.Name);
            }
        }

        private static Constant ReadConstant(XmlReader reader)
        {
            if (!"Constant".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Constant节点");
            }
            string sValue = null;
            if (reader.MoveToAttribute("Value"))
            {
                sValue = reader.Value;
            }
            Constant constant = Constant.Null;
            if (!string.IsNullOrEmpty(sValue) && reader.MoveToAttribute("Type"))
            {
                string sType = reader.Value;
                Type type = Type.GetType(sType);
                if (type == typeof (string))
                {
                    var value = Convert.ToString(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (bool))
                {
                    var value = Convert.ToBoolean(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (int))
                {
                    var value = Convert.ToInt32(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (decimal))
                {
                    var value = Convert.ToDecimal(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (DateTime))
                {
                    var value = Convert.ToDateTime(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (Guid))
                {
                    var value = new Guid(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (Byte))
                {
                    var value = Convert.ToByte(sValue);
                    constant = new Constant(value);
                }
                else if (type == typeof (Byte[]))
                {
                    throw new NotImplementedException();
                }
                else
                {
                    throw new FormatException("未识别的常量类型：" + sType);
                }
            }
            return constant;
        }

        private static Macro ReadMacro(XmlReader reader)
        {
            if (!"Macro".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Macro节点");
            }
            throw new NotImplementedException();
        }

        private static Field ReadField(XmlReader reader)
        {
            if (!"Field".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Field节点");
            }
            if (!reader.MoveToAttribute("Path"))
            {
                throw new FormatException("节点未包含Path属性");
            }
            string path = reader.Value;
            if (!reader.MoveToAttribute("Type"))
            {
                throw new FormatException("节点未包含Type属性");
            }
            string sType = reader.Value;
            var type = Type.GetType(sType);
            if (type == null)
            {
                throw new FormatException("未能识别的字段类型："+sType);
            }
            var field = new Field(path, type);
            return field;
        }

        private static Formular ReadFormular(XmlReader reader)
        {
            if (!"Formular".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Formular节点");
            }
            if (!reader.MoveToAttribute("Operator"))
            {
                throw new FormatException("节点未包含Operator属性");
            }
            string sOperator = reader.Value;
            var operatr = ParseOperator<MathOperator>(sOperator);
            var unitaried = false;
            if (!reader.MoveToAttribute("Unitaried"))
            {
                unitaried = bool.Parse(reader.Value);
            }
            Factor left = null, right = null;
            if (reader.MoveToNextElement())//读取Left属性
            {
                left = ReadFactor(reader);
            }
            //如果是Count/Sum运算符，则没有Right因子
            if (!(operatr == MathOperator.Count && operatr == MathOperator.Sum))
            {
                if (reader.MoveToNextElement())//读取Right属性
                {
                    right = ReadFactor(reader);
                }
            }
            var formular = new Formular(left, operatr, right);
            return formular;
        }

        private static OrderClause ReadOrderClause(XmlReader reader)
        {
            if (!"OrderBy".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非OrderClause节点");
            }
            var orderClause = new OrderClause();
            while(reader.MoveToNextElement("OrderBy"))
            {
                if (reader.Name != typeof(Field).Name && reader.Name != typeof(Formular).Name)
                    break;
                var orderElement = ReadOrderElement(reader);
                if (orderElement == null)
                    break;
                orderClause.Add(orderElement);
            }
            return orderClause;
        }

        private static OrderElement ReadOrderElement(XmlReader reader)
        {
            if (!reader.MoveToAttribute("Rule"))
                return null;
            var rule = (OrderRule) Enum.Parse(typeof(OrderRule), reader.Value);
            reader.MoveToElement();//读取Rule属性后，必须移动到属性所在节点，否则后续读取将不正确
            var factor = ReadFactor(reader);
            if (factor is Field)
            {
                return new OrderElement(factor as Field, rule);
            }
            else if (factor is Formular)
            {
                return new OrderElement(factor as Formular, rule);
            }
            else
            {
                throw new Exception("不支持使用该类型的因子进行数据排序：" + factor.GetType().FullName);
            }
        }

        private static Pagination ReadPagination(XmlReader reader)
        {
            if (!"Pagination".Equals(reader.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new Exception("当前节点非Pagination节点");
            }
            int size, index;
            if (!reader.MoveToAttribute("PageSize"))
                throw new Exception("缺少必要的属性：PageSize");
            size = int.Parse(reader.Value);
            if (!reader.MoveToAttribute("PageIndex"))
                throw new Exception("缺少必要的属性：PageIndex");
            index = int.Parse(reader.Value);
            return new Pagination(size, index);
        }

        #endregion
    }

}
